from abc import ABC, abstractmethod
from typing import List, Dict, Optional

import requests
from bs4 import BeautifulSoup

from buondua.models import db


class BaseParser(ABC):
    """
    Базовый парсер, который определяет основную логику для получения данных и сохранения в базу данных.
    """

    REQUEST_HEADERS = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    }

    BASE_URL = 'https://buondua.com'

    DEFAULT_ENCODING = 'utf-8'

    def __init__(self):
        self.document: Optional[BeautifulSoup] = None

    def parse(self, url, *, do_save=False, **kwargs) -> Optional[Dict]:
        '''Запуск парсинга. Возвращает список словарей (ключи - поля модели)'''
        from copy import deepcopy
        if not self.check_url(url):
            raise Exception("URL is not valid")
        self.document = self.get_document(url)
        data = self.extract()
        if do_save:
            self.save(deepcopy(data))

        return data

    def get_document(self, url) -> BeautifulSoup:
        '''Конвертирует сырые данные в DOM-like'''
        data = self.open_url(url)
        return BeautifulSoup(data, 'html.parser')

    def open_url(self, url) -> Optional[bytes]:
        '''Загружает страницу из интеренета'''
        with requests.Session() as session:
            adapter = requests.adapters.HTTPAdapter(max_retries=10)
            session.mount('https://', adapter)
            response = session.get(
                url,
                headers=self.REQUEST_HEADERS,
                timeout=5
            )
            if not response.ok:
                raise Exception("Failed get page content: code %s" % response.status_code)
            return response.content

    @abstractmethod
    def save(self, data: Optional[Dict]):
        '''Сохраняет данные в базу данных'''
        raise NotImplementedError()

    @abstractmethod
    def check_url(self, url) -> bool:
        '''Проверяет валидность URL'''
        raise NotImplementedError()

    @abstractmethod
    def extract(self) -> Optional[Dict]:
        '''Извлекает все нужные данные со страницы и возвращает их как список словарей (ключи - поля модели)'''
        raise NotImplementedError()
